|====================================| | | | TELEMACHOS proudly presents : | | | | Part 5 of the PXD trainers - | | | | SVGA using VESA 1.2 | | | | | |====================================| ___---__--> The Peroxide Programming Tips <--__---___ <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> Intoduction ----------- Hi folks... It's been a while, I know, but I have been busy living my REAL life :) (YEAH, I know : REAL LIFE SUXX!) There have been quite a few people mailing me about my last two tutorials and one of them made me discover a little error in my gouraud routines. Actually it's not an error - I just presume that there is a maximum of 64 colors in the shading table. If using more a few variables / regs will overflow resulting in graphical errors. The problems can be fixed by reducing the shl 8 statement to shl 7 when calculating the fixed point step values. Then later on when the vars has been calculated as 9.7 fixed point values you just have to multiply them by two to get them back to 8.8 fixed point. Now things should work correct. Also, in the gouraud polygonside scanner change the type of the variable "color" from integer to word. This tutorial will be on SVGA graphic using the VESA 1.2 BIOS implemention. In the past I have seen quite a few shareware SVGA programming libs - but so far all of them has sucked pretty much. Either they presume a granularity of 64K - or they are written in pure asm without any decent comments - making them virtually impossible to learn from. Or they use BIOS calls to switch banks which suck just as much as code without comments. So what we'll do is to build a small pascal/asm SVGA lib that supports *ALL* SVGA cards on the market (at least we can try :) ). Also I'll try to explain everything about SVGA in a way so that the reader actually understands VESA-coding after reading this text. If you are bored with theory stop reading right now and skip to the headline called "THE CODE". But I strongly recommend you to read the entire text - otherwise you'll be coding VESA graphics in half an hour but not understand what you're doing :) BTW. Some lamer seems to find it extremely funny to reset the hit-counter on my homepage. If you're reading this : Please don't do it again - it makes me very unhappy and I lie fever-striken on my bed for several days each time it happen. If you really enjoy resetting counters THAT much drop me a mail and I'll code you an internet-hit-counter-resetter-simulater FOR FREE!!!!!!! If any of you NON-lamers who reads this - you know, the kind of people who spend their time actually CODING instead of resetting counters, spreading virusses and beating their little brothers - I would like to know if any of you know how to make a hit-counter that cannot be reset by the weak-minded people of the net. If you want to get in contact with me, there are several ways of doing it : 1) E-mail me : tm@image.dk 2) Snail mail me : Kasper Fauerby Saloparken 226 8300 Odder Denmark 3) Call me (Voice ! ) : +45 86 54 07 60 Get this serie from the major demo-related FTP-sites - currently : GARBO ARCHIVES (forgot the address) : /pc/programming/ ftp.teeri.oulu.fi : /msdos/programming/docs/ ftp.cdrom.com : something with demos/incomming/code..... Or grap it from my homepage : Telemachos' Codin' Corner http://www.image.dk/~tm WHAT IS VESA AND WHY DO WE NEED IT ? ------------------------------------ Well... as you all know the standard mode $13 is a linear mode taking up 64000 bytes of memory. This fits into a single segment and therefore it is extremely easy to code. When mode $13 was introduced all agreed on using segment $a000 as base for the mode. Therefore our mode $13 code will work on EVERY SINGLE VGA-card available. When SVGA was introduced for the first time it was IBM who developed a very expensive GFX-card capable of doing extremely high resolutions - for that time, that is. But IBM never released the register information for programming the card. In time other companies developed SVGA cards - each with their own way of laying out the gfx-cards registers. The result was that it became almost impossible to code applications for SVGA - if the application was to support the different gfx cards the programmers had to do routines for EVERY SINGLE gfx card on the market. The result was that most programmers chosed not to support SVGA at all. The high resolution was'nt worth the time spend on doing the code. VESA saw the problem and came up with a BIOS extension that supplied some standard routines for coding the different SVGA cards available at the time being. For a long time it has been VESA 1.2 that has been the standard in SVGA coding. But now UniVBE 5.3 has been released making VESA 2.0 available on almost every graphic card out there. But for us pascal coders VESA 2.0 is pretty uninterresting. First of all : The main improvement in VESA 2.0 if something called Linear Frame Buffer (LFB) This means that you map your entire VGA-memory into one single linear buffer. Then you can adress each pixel on the SVGA screen lineary - just as in good old mode $13. But this is obviously only available to those coding in protected mode - and I take it that most of my readers are'nt. Another thing is that most gfx cards out there only has VESA 1.2 installed - so in order to use VESA 2.0 you'll have to assume that the user owns UniVBE 5.3 So for now we'll stick to good old VESA 1.2 THE WORKING OF A SVGA CARD - THE THEORY FOR OUR CODE ----------------------------------------------------- As mentioned before each pixel takes up 1 byte of memory. In mode $13 this is OK as the screen is only 320 X 200 = 64000 bytes. This way the video memory needed for a single screen of graphic fits into one single segment. But a SVGA mode takes up much more space. Lets take the standard 640X480 SVGA mode. This mode takes up 640 X 480 = 307200 bytes = 4,6875 segments of RAM. Hmm... how do we adress all this memory. The modenr for this mode is $101 Try and set this mode using the following VESA code : (I'll explain the code later on) asm mov ah,4Fh mov al,02h mov bx,$101 int 10h end; Now try and address the video mem via the good old $a000 segment.... Yes, just fill the entire segent with some color. Woila! You have just plotted your first pixels in 640 X 480 X 256. But as you might have discovered you are limited to accessing only the uppermost part of the screen. The narrow band of graphic you see takes up exactly 1 segment of memory. What you have just discovered is the greatest pain in SVGA programming : the need of windowing. Imagine the entire SVGA-memory laid out lineary. You can form it to many different sizes. One size is 640 pixels wide, another 800 and yet another 1024. But all the time you only have access to 1 segment of the memory. Think of this segment as a window you can look through into the entire video memory. Fortunately you can move this window to many different positions in the video memory. If you move it around enough you'll discover that you can see every byte in video memory through it - just only one segment at a time. We call all those different window positions for banks. Well, VESA does provide us with standard procedures to set the graphic modes, test them and move the bank window but still SVGA is a little complicated. First of all there is the GRANULARITY of the video card. The granularity decides how the window can be moved around in the video memory. A granularity of 64K means that the window can be moved only in 64K chunks - ie. an entire window at a time. Some cards have finer granularity - Cirrus Logic fx. has a granularity of 4K which allows the windows to overlap. Each window position is called a bank even though some of the memory can be accesed through multiple window positions. The window size is an entirely different matter from the granularity. On most cards the window size is 64K (actually on every card I have seen so far) but some cards implement 128K. This is however not important to us as the greatest granularity is 64K - so as long the window is not below 64K in size we can acces all memory by moving the window. It is pretty safe to assume that the video memory is accesed from the $a000 segment but to be sure one should always test this by making a BIOS call before trying to write to memory. Some cards have multiple windows - We call them window A and Window B. This is because some cards have ReadOnly access in one window and WriteOnly in another. So this is another thing to check : Can we do both our reads and writes in the same window ? THE VESA FUNCTIONS EXPLAINED ----------------------------- All VESA functions is reached from a sub-function of the BIOS interrupt $10. This subfunction is 4Fh so in all interrupt calls we must load the ah register with 4Fh and the al register with the VESA function number we want. I'll just run over the different VESA BIOS functions and then do the code in the CODE section. Function 00h - Return SVGA info --------------------------------- Input : AH : 4Fh AL : 00h ES:DI : Pointer to 256bytes buffer. Returns : AX : Status The status register is set up as follows : AL == 4Fh: Function is supported Al != 4Fh: Function is not supported AH == 00h: Function call successful AH == 01h: Function call failed Now, as we see we load ah with our VESA subfunction number and AL with the VESA function number. ES:DI must point to a 256bytes buffer. This can be any buffer of this size. Fx. buffer : Array[0..255] of byte; But using a buffer like this leaves us with an enormous amount of converting from byte to word and so on... So I'll give you a structure to use instead : TYPE ListOfAvailModesT = Array[0..255] of word; {terminated by -1 ($FFFF) } ListOfAvailModesP = ^ListOfAvailModesT; VESAInfoT = record VESASignature : array[0..3] of byte; VESAVersion : word; OEMStringPtr : Pchar; Capabilities : array[0..3] of byte; VideoModePtr : ListOfAvailModesP; TotalMemory : word; Reserved : Array[0..235] of byte; end; The VESASignature is 4 bytes and when converted to a string they must form the word 'VESA'. Otherwise it is not a valid VESA-structure. The VESAVersion is a word with the high-byte being the major version number and the low byte being the minor version number. OEMStringPtr is a pointer to a 0-terminated String which contains the ident string of the SVGA card. But we are in luck - in pascal the type Pchar means a pointer to such a string, so we don't have to think any more about this one. Capabilities is mostly unused. If bit 0 in the first byte is set it means that the DAC can be reprogrammed to another DAC width (standard is 6-bit) VideoModePtr : Now this is a little tricky. This is a pointer to a list of words - each word containing a valid graphic mode on the card. The list is terminated by -1 ($FFFF). TotalMemory contains the number of 64K blocks of RAM installed on the card. The reserved field is reserved for future VESA expansions. This function is the first function your application should run for checking if VESA is available at all. Function 01h - Return VESA MODE information ------------------------------------------- Input : AH = 4Fh AL = 01h CX = Mode number (must be from the mode list from func 00h) ES:DI = pointer to 256 bytes buffer Returm AX = Status Again I'll give you a structure to use for the 256 bytes buffer. I'll leave out some fields for true-color stuff that we will not discuss in this text. TYPE ModeAttributesT = (Available, Reserved, BIOSFunctionsSupport, color, graphic, bit5, bit6, bit7, bit8); WindowAttributesT = (Supported,R,W); {rest of the bits are unused...} VESAModeInfoT = record ModeAttributes : set of ModeAttributesT; WinAAttributes : set of WindowAttributesT; WinBAttributes : set of WindowAttributesT; WinGranularity : word; WinSize : word; WinASegment : word; WinBSegment : word; BankSwitch : procedure; BytesPerScanLine : word; {Xtended Information} XResolution : word; YResolution : word; XCharSize : byte; YCharSize : byte; NumberOfPlanes : byte; BitsPerPixel : byte; NumberOfBanks : byte; MemoryModel : byte; BankSize : byte; NumberOfImagePages : byte; Reserved : Array[0..225] of byte; {reserved is for something we won't think about now... true color and stuff... } end; WOW.... This sure is a tough one to get through. Well.. to start from the top. ModeAttributes : This is a word with every bit meaning something different. That's why I made the ModeAttributesT. It contains different information about the mode from CX. If bit 0 is set the mode is available - if not : Your guess. The rest of this field should be obvious from the names in ModeAttributes : is it a color mode ? Is it a graphic or text mode? The BIOSSupport field decided if you can use BIOS scroll, TTY output and BIOS pixel output in the selected mode. WinAAttributes and WinBAttributes : These are pretty important as they tell you if you can read or write to the two windows (A and B). Some cards has only one window availble so a window CAN be Read AND Writeable. WinGranularity : This field contains the granularity in KB. WinSize : The Size of the window in KB. WinASegent and WinBSegment : This decides where in memory the window base is placed. Usually WinASegment will be $A000 - but check it out to be sure. If a window reports $0000 as address DON'T use that address :) BankSwitch : This is a pointer to the hardware bankswitch function. A far call can be made to this address for quick bankshifting. Lucky us - pascal allows us to use the type "procedure" here so we don't have to worry about the pointer stuff... just call this field from an assembler routine. More on this facility under function 05h later on. BytesPerScanLine : The logical number of bytes pr y-line in memory. This is usually the same as the maximum x-value in the graphic mode. The Xtended fields should be pretty self-explaining. One word of warning though. I have experienced some problems with the NumberOfBanks and BankSize fields on my Cirrus Logic card. Don't trust these fields blindly. function 02 - Set VESA mode ---------------------------- Input : AH = 4Fh AL = 02h BX = Video Mode bit 15 : 1 = Don't clear video RAM 0 = Clear video RAM Returns : AX = Status OK.. Use this one to set the VESA modes. The modes is as follows : GRAPHICS TEXT 15-bit 7-bit Resolution Colors 15-bit 7-bit Columns Rows mode mode mode mode number number number number ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 100h - 640x400 256 108h - 80 60 101h - 640x480 256 109h - 132 25 102h 6Ah 800x600 16 10Ah - 132 43 103h - 800x600 256 10Bh - 132 50 10Ch - 132 60 104h - 1024x768 16 105h - 1024x768 256 106h - 1280x1024 16 107h - 1280x1024 256 10Dh - 320x200 32K (1:5:5:5) 10Eh - 320x200 64K (5:6:5) 10Fh - 320x200 16.8M (8:8:8) 110h - 640x480 32K (1:5:5:5) 111h - 640x480 64K (5:6:5) 112h - 640x480 16.8M (8:8:8) 113h - 800x600 32K (1:5:5:5) 114h - 800x600 64K (5:6:5) 115h - 800x600 16.8M (8:8:8) 116h - 1024x768 32K (1:5:5:5) 117h - 1024x768 64K (5:6:5) 118h - 1024x768 16.8M (8:8:8) 119h - 1280x1024 32K (1:5:5:5) 11Ah - 1280x1024 64K (5:6:5) 11Bh - 1280x1024 16.8M (8:8:8) Function 03h - Return current Video Mode ----------------------------------------- Input : AH = 4Fh AL = 03h Returns : AX = Status BX = Current Video mode Not much to say about this one I guess... Function 05h - Set Window position / Bank Switch ------------------------------------------------- Input : AH = 4Fh AL = 05h BH = 00h BL = Window nr Window A = 0 Window B = 1 DX = Window position in granularity units. This means bank number. Returns : AX = Status Now, this routine sets the window position that we talked about earlier. The key to making a good VESA unit is to make certain that it is the correct bank numbers that is passed to this function. On a SVGA card with a granularity of 64K each bank matches one segment of memory and one potion of the screen. If we want to clear out Screen we usually selects bank nr 0 and then fill the $A000 segment with one color. Then we want to move on to the next section of the screen. So we increase our banknr by 1 and do the same trick. But WAIT A SECOND!! This will ONLY work with granunarities of 64K. 'Cause on cards with a granularity of fx. 4K we might have set the window to bank 0. And we might have filled the entire window - but by doing this we have filled SEVERAL banks! With a granularity of 4K we have filled 64K / 4K = 16 banks! So the next bank we should set should NOT be 1 but 16! This is where most errors I have seen in SVGA libs lies. As we all know calling BIOS is pretty SLOW! And with small granularities we might have to do it MANY times during a screen-update so we're pretty lucky that we have the brain to use the far function address we got from function 01h. When using it we don't have to set the AX register before call. Just the BX and DX register. Beware though : Both AX and DX is destroyed in the call so be sure to save the information in them if you wanna reuse them. Function 06h - Set/Get logical Scanlenght ------------------------------------------ Input : AH = 4Fh AL = 06h BL = 00h {Set scanlength} CX = Desired width in pixels Returns : AX = Status BX = Bytes per Scanline CX = Actual pixels per Scanline DX = Maximum number of scanlines based on mode and memory Input : AH = 4Fh AL = 06h BL = 01h {Get scanlength} Returns : AX = Status BX = Bytes per Scanline CX = Actual pixels per Scanline DX = Maximum number of Scanlines. Now this function can be VERY useful. It allows you to set a logical width in video memory. It is based on this width that you calculate the offset for a pixel - just like in mode $13h. If you set this value to a power of 2 you can make sure that there will never be any bank-crossing along a horizontal scanline. This can speed up your code if you update the screen Scanline per Scanline 'cause you only have to do a bank check once per Scanline - and not once per pixel as you would in fx. mode 640 X 480! Also you can use function 07h to place the actual screen anywhere in this logical memory and this way do hardware scrolls! If you plan on using 640 X 480 and you know that your video card has 1MB of RAM installed you could set the logical scanlength to 1024 and thereby ease the offset calculation from : ofs := y shl 9 + y shl 7 + X to ofs := y shl 10 + X; It's a waste of memory, but if you are smart you store something in the memory outside the screen. Function 07h - Set / Get display start ---------------------------------------- Input : AH = 4Fh AL = 07h BH = 00h BL = 00h {set display start} CX = Xpos DX = Ypos Returns : AX = Status Input : AH = 4Fh AL = 07h BL = 01h {get display start} Returns : AX = Status BH = 00h CX = Xpos DX = Ypos This one sets the position of the screen in video memory. Function 08h - Set/Get DAC pallette control -------------------------------------------- Input : AH = 4Fh AL = 08h BL = 00h {Set DAC pallette width} BH = desired number of bits per primary color Returns : AX = Status BH = Current number of bits per primary color Input : AH = 4Fh AL = 08h BL = 01h {get DAC pallette width} Returns : AX = Status BH = Current number of bits per primary color This function sets the DAC pallette width - check if this function is available by checking bit 0 in the Capabilities field in VESAInfo. THE CODE : ----------- First lets get the VESA information and save it in a variable called VESAInfo : FUNCTION GetVESAInfo : boolean; Assembler; asm mov ah,4Fh {The usual VESA sub-function number} mov al,00h {This is VEAS function 00h } lea di,VESAInfo {load the address of VESAInfo into es:di} int 10h {call the BIOS interrupt} cmp ah,0 {ah returns 0 if succes} jne @fail mov ax,1 jmp @out @fail: mov ax,0 {the ax register will be passed to the function} @out: end; The same way we get the Info for the mode we wanna set : FUNCTION GetVESAModeInfo(mode : word) : boolean; Assembler; asm mov ah,4Fh mov al,01h {VESA function 01h} xor cx,cx mov cx,mode {load the desired mode in cx} lea di,VESAModeInfo {load the address to VESAModeInfo into es:di} int 10h {call BIOS video interrupt} cmp ah,0 jne @fail mov ax,1 jmp @out @fail: mov ax,0 {we put the result in ax } @out: end; OK... Now we need to be able to switch the banks : Procedure SetBank(nr : word); Assembler; asm xor bl,bl {Window A selected} mov dx,nr call [VESAModeInfo.BankSwitch] {here we call the bankSwitch procedure} mov dx,nr {dx is destroyed by far procedure call} mov cur_page,dx {update global page variable} end; Now this routine assumes that Window A is Read/Writeable. BX desides which window is active bl = 0 means Window A and bl = 1 means Window B. It is a good idea to have a global variable that keeps track of which bank is active at the moment so you don't change to the bank that is allready active! This done I think we need to set the mode up : FUNCTION SetVESAMode(mode : word) : boolean; Assembler; asm mov ax,03h int 10h {start from textmode} mov ah,4Fh mov al,02h mov bx,mode int 10h cmp ah,0 jne @fail {set the VESA mode through VESA function 02h} mov ah,4Fh mov al,05h xor bx,bx mov dx,0 int 10h {set bank 0 from BIOS call this time - to be safe } mov cur_page,0 {initialize global page variable to bank 0} cmp ah,0 jne @fail mov ax,1 jmp @out @fail: mov ax,0 {return fail or succes to function} @out: end; Now lets plot a pixel, shall we ?? PROCEDURE VESAputpixel(x, y : WORD; c : BYTE); VAR bank : WORD; offs : longint; BEGIN offs := LONGINT(y) * VESAModeInfo.Xresolution + x; bank := offs SHR (16-BankShiftModifier); offs := offs - (bank SHL (16-BankShiftModifier)); IF bank <> Cur_page THEN {page = global var - active page} BEGIN cur_page := bank; ASM Xor bl,bl mov dx,bank call [VESAModeInfo.BankSwitch] END; END; ASM MOV AX, $A000 MOV ES, ax MOV DI, WORD(offs) MOV AL, c MOV ES:[DI], AL END; END; Now in this routine I use a variable called BankShiftModifier. This is because the routine assumes a granularity of 64K when using the shr 16 and shl 16 statements. With a granularity of fx. 4K it should be shr 12 and shl 12. So we'll have to calculate the modifier when we recieve the information about the granularity from function 01h. Case VESAModeInfo.WinGranularity of 1 : BankShiftModifier := 6; 2 : BankShiftModifier := 5; 4 : BankShiftModifier := 4; 8 : BankShiftModifier := 3; 16 : BankShiftModifier := 2; 32 : BankShiftModifier := 1; 64 : BankShiftModifier := 0; end; {With granularities smaller than 64 but with page-size 64K the banknr for each full segment of SVGA graphic will not be 0,1,2,3 but multiplied with BankMult ( 2^BankShiftModifier for easy bit shifting) } BankMult := 64 div VESAModeInfo.WinGranularity; Now, I know of granularities of 64K, 32K and 4K... But it can't hurt to have the other posibilities as well... you never know with SVGA. The Variable BankMult is used for our next routine - The clear routine. As mentioned when discussing function 05h the banks won't be 0,1,2,3,4 and so on when dealing with granularities smaller than 64K. Each Segment filled is the same as 16 banks filled with a granularity of 4K So here goes : The ClearScreen routine : PROCEDURE ClearScreen(color : byte); VAR Xres, Yres : longint; number_of_segments : longint; i : integer; BEGIN Xres := VESAModeInfo.Xresolution; Yres := VESAModeInfo.Yresolution; number_of_segments := Round(((Xres * Yres)/65536)+0.5)-1; for i := 0 to number_of_segments do BEGIN SetBank(i*BankMult); ASM mov cx, 32768; mov ax,$A000 mov es,ax xor di,di mov al,[color] mov ah,al rep stosw END; END; END; And now the final two small routines : Procedure SetLogicalScanline(width : word); Assembler; asm mov ah,4Fh mov al,06h mov bl,0 mov cx,width int 10h end; Procedure SetScreenPosition(x,y : word); Assembler; asm mov ah,4Fh mov al,07h xor bx,bx mov cx,[x] mov dx,[y] int 10h end; LAST REMARKS ------------- Well, that's about all for now. Hope you found this doc useful - and BTW : If you DO make anything public using these techniques please mention me in your greets or where ever you se fit. I DO love to see my name in a greeting :=) Now, this done you should all be able to code some pretty stunning SVGA games/ demos. We can no longer say anything else : SVGA is the future of all graphic programming. A few things to take note of : All the routines presented here in this text are very general. The ClearScreen will do any resolution - same thing with the putpixel routine. But I have sacrificed speed to achieve this! If you use the routines in games/demos where you KNOW what resolution it will run at you should modify the routines and optimize them to meet those specific resolutions! Fx. I use a normal * to calculate the offset in the putpixel. This should off cause be changed to shl's when dealing with a predefined gfx-mode. But what now ?? If you have any good ideas for a subject you wish to see a tutorial on please mail me. If I like the idea (and know anything about it :) ) I'll write a tut on it. I have spoken of a tutorial on interrupts for quite a while now.... maybe it'll come out soon - but I also plan on doing some graphical effects. MAIL ME!! Keep on coding... Telemachos - August '97.